/*
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Authors:
 *	lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include <linux/backlight.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>

#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_panel.h>

#define WRITEREG32(reg, val)	writel(val, (void __iomem *)reg)
#define READREG32(reg)		readl((void __iomem *)reg)

/* #define ENABLE_1080P */	/* need more CMA space, modify defconfig */
/* #define ENABLE_720P */

#define T1_WIDTH	(720)
#define T1_HEIGHT	(480)
#define T1_VIC		(2)
#define T1_HBP		(60)
#define T1_HFP		(16)
#define T1_HSW		(62)
#define T1_HACT		(T1_WIDTH)
#define T1_VBP_F1	(30)
#define T1_VFP_F1	(9)
#define T1_VSW_F1	(6)
#define T1_VACT_F1	(T1_HEIGHT)
#define T1_VT_F1	(T1_VBP_F1 + T1_VFP_F1 + T1_VSW_F1 + T1_VACT_F1)
#define T1_VBP_F2	(0)
#define T1_VFP_F2	(0)
#define T1_VSW_F2	(0)
#define T1_VACT_F2	(0)
#define T1_VT_F2	(T1_VBP_F2 + T1_VFP_F2 + T1_VSW_F2 + T1_VACT_F2)
#define T1_DCLK		(27000000)

#ifdef ENABLE_720P
#define T2_WIDTH	(1280)
#define T2_HEIGHT	(720)
#define T2_VIC		(19)
#define T2_HBP		(220)
#define T2_HFP		(440)
#define T2_HSW		(40)
#define T2_HACT		(T2_WIDTH)
#define T2_VBP_F1	(20)
#define T2_VFP_F1	(5)
#define T2_VSW_F1	(5)
#define T2_VACT_F1	(T2_HEIGHT)
#define T2_VT_F1	(T2_VBP_F1 + T2_VFP_F1 + T2_VSW_F1 + T2_VACT_F1)
#define T2_VBP_F2	(0)
#define T2_VFP_F2	(0)
#define T2_VSW_F2	(0)
#define T2_VACT_F2	(0)
#define T2_VT_F2	(T2_VBP_F2 + T2_VFP_F2 + T2_VSW_F2 + T2_VACT_F2)
#define T2_DCLK		(74250000)
#endif

#ifdef ENABLE_1080P
#define T3_WIDTH	(1920)
#define T3_HEIGHT	(1080)
#define T3_VIC		(16)
#define T3_HBP		(148)
#define T3_HFP		(88)
#define T3_HSW		(44)
#define T3_HACT		(T3_WIDTH)
#define T3_VBP_F1	(36)
#define T3_VFP_F1	(4)
#define T3_VSW_F1	(5)
#define T3_VACT_F1	(T3_HEIGHT)
#define T3_VT_F1	(T3_VBP_F1 + T3_VFP_F1 + T3_VSW_F1 + T3_VACT_F1)
#define T3_VBP_F2	(0)
#define T3_VFP_F2	(0)
#define T3_VSW_F2	(0)
#define T3_VACT_F2	(0)
#define T3_VT_F2	(T3_VBP_F2 + T3_VFP_F2 + T3_VSW_F2 + T3_VACT_F2)
#define T3_DCLK		(148500000)
#endif

#define MODES_NUM	(3)
#define DCS_MDELAY_FLAG (0)
#define LANE_NUM	(4)

struct panel_lt8912b_desc {
	const struct drm_display_mode *modes;
	unsigned int num_modes;
	const struct display_timing *timings;
	unsigned int num_timings;

	unsigned int bpc;

	struct {
		unsigned int width;
		unsigned int height;
	} size;

	/**
	 * @prepare: the time (in milliseconds) that it takes for the panel to
	 *           become ready and start receiving video data
	 * @enable: the time (in milliseconds) that it takes for the panel to
	 *          display the first valid frame after starting to receive
	 *          video data
	 * @disable: the time (in milliseconds) that it takes for the panel to
	 *           turn the display off (no content is visible)
	 * @unprepare: the time (in milliseconds) that it takes for the panel
	 *             to power itself down completely
	 */
	struct {
		unsigned int prepare;
		unsigned int enable;
		unsigned int disable;
		unsigned int unprepare;
	} delay;

	u32 bus_format;
	unsigned long flag;
	unsigned int lanes;
	enum mipi_dsi_pixel_format format;
};

struct panel_lt8912b {
	struct drm_panel base;
	bool prepared;
	bool enabled;

	struct device *dev;
	const struct panel_lt8912b_desc *desc;

	struct backlight_device *backlight;
	struct regulator *supply;

	int reset_gpio;
	int power_gpio;
	int sel_gpio;
	u32 modes_idx;
};

static struct i2c_client *panel_i2c_client[3];

static u32 lt8912b_init_common_code[] = {
	/* DigitalClockEn(); */
	/* define _HDMI_Output_ */
	3, 0x90, 0x08, 0xff, /* Register address: 0x08; Value: 0xff */
	3, 0x90, 0x09, 0x81,
	3, 0x90, 0x0a, 0xff,
	3, 0x90, 0x0b, 0x64,
	3, 0x90, 0x0c, 0xff,
	3, 0x90, 0x44, 0x31,	/* Close LVDS output */
	3, 0x90, 0x51, 0x1f,
	/* TxAnalog(); */
	3, 0x90, 0x31, 0xa1,
	3, 0x90, 0x32, 0xbf,
	/* 0x03 Open HDMI Tx, 0x00 Close HDMI Tx */
	3, 0x90, 0x33, 0x17,
	3, 0x90, 0x37, 0x00,
	3, 0x90, 0x38, 0x22,
	3, 0x90, 0x60, 0x82,
	3, 0x90, 0x3a, 0x00,
	/* CbusAnalog(); */
	3, 0x90, 0x39, 0x45,
	3, 0x90, 0x3b, 0x00,
	/* HDMIPllAnalog(); */
	3, 0x90, 0x44, 0x31,
	3, 0x90, 0x55, 0x44,
	3, 0x90, 0x57, 0x01,
	3, 0x90, 0x5a, 0x02,
	/* MipiBasicSet(); */
	3, 0x92, 0x10, 0x01, /* 0x05 */
	3, 0x92, 0x11, 0x08, /* 0x12 */
	3, 0x92, 0x12, 0x04,
	/* 00 4 lane , 01 lane , 02 2 lane , 03 3 lane */
	3, 0x92, 0x13, (LANE_NUM % 0x04),
	3, 0x92, 0x14, 0x00,
	3, 0x92, 0x15, 0x00,
	3, 0x92, 0x1a, 0x03,
	3, 0x92, 0x1b, 0x03,
	3, 0x92, 0x1e, 0x67, /* hs/vs/de pol hdmi sel pll sel  */
	3, 0x92, 0x2f, 0x0c, /* fifo_buff_length 12  */
	/* DDSConfig(); */
	3, 0x92, 0x4e, 0x52,
	3, 0x92, 0x4f, 0xde,
	3, 0x92, 0x50, 0xc0,
	3, 0x92, 0x51, 0x80,
	3, 0x92, 0x51, 0x00,
	3, 0x92, 0x1f, 0x5e,
	3, 0x92, 0x20, 0x01,
	3, 0x92, 0x21, 0x2c,
	3, 0x92, 0x22, 0x01,
	3, 0x92, 0x23, 0xfa,
	3, 0x92, 0x24, 0x00,
	3, 0x92, 0x25, 0xc8,
	3, 0x92, 0x26, 0x00,
	3, 0x92, 0x27, 0x5e,
	3, 0x92, 0x28, 0x01,
	3, 0x92, 0x29, 0x2c,
	3, 0x92, 0x2a, 0x01,
	3, 0x92, 0x2b, 0xfa,
	3, 0x92, 0x2c, 0x00,
	3, 0x92, 0x2d, 0xc8,
	3, 0x92, 0x2e, 0x00,
	3, 0x90, 0x03, 0x7f,
	DCS_MDELAY_FLAG, 100,
	3, 0x90, 0x03, 0xff,
	3, 0x92, 0x42, 0x64,
	3, 0x92, 0x43, 0x00,
	3, 0x92, 0x44, 0x04,
	3, 0x92, 0x45, 0x00,
	3, 0x92, 0x46, 0x59,
	3, 0x92, 0x47, 0x00,
	3, 0x92, 0x48, 0xf2,
	3, 0x92, 0x49, 0x06,
	3, 0x92, 0x4a, 0x00,
	3, 0x92, 0x4b, 0x72,
	3, 0x92, 0x4c, 0x45,
	3, 0x92, 0x4d, 0x00,
	3, 0x92, 0x52, 0x08,
	3, 0x92, 0x53, 0x00,
	3, 0x92, 0x54, 0xb2,
	3, 0x92, 0x55, 0x00,
	3, 0x92, 0x56, 0xe4,
	3, 0x92, 0x57, 0x0d,
	3, 0x92, 0x58, 0x00,
	3, 0x92, 0x59, 0xe4,
	3, 0x92, 0x5a, 0x8a,
	3, 0x92, 0x5b, 0x00,
	3, 0x92, 0x5c, 0x34,
	3, 0x92, 0x1e, 0x4f,
	3, 0x92, 0x51, 0x00,
	/* AudioIIsEn();  IIS Input */
	3, 0x90, 0xB2, 0x01,
	3, 0x94, 0x06, 0x08,
	3, 0x94, 0x07, 0xF0,
	3, 0x94, 0x34, 0xE2,
	3, 0x94, 0x3c, 0x41,
	/* Packet config() */
	3, 0x94, 0x3e, 0x0A,
	/* MIPIRxLogicRes(); */
	3, 0x90, 0x03, 0x7f,
	DCS_MDELAY_FLAG, 100,
	3, 0x90, 0x03, 0xff,
	/* MIPIRxLogicRes(); */
	3, 0x92, 0x51, 0x80,
	DCS_MDELAY_FLAG, 100,
	3, 0x92, 0x51, 0x00,

	3, 0x94, 0x44, 0x10, /* avi packet output RGB0x10 */
	3, 0x94, 0x45, 0x2A, /*0x19:4:3 ; 0x2A : 16:9 */
};

static u32 lt8912b_init_code[MODES_NUM][72] = {
	{
		3, 0x92, 0x18, (u8)(T1_HSW % 256), /* hsync  */
		3, 0x92, 0x19, (u8)(T1_VSW_F1 % 256), /* vsync  */
		3, 0x92, 0x1c, (u8)(T1_HACT % 256), /* H_active[7:0]  */
		3, 0x92, 0x1d, (u8)(T1_HACT / 256), /* H_active[15:8]  */
		/* H_total[7:0] */
		3, 0x92, 0x34, (u8)((T1_HBP + T1_HFP + T1_HSW + T1_HACT) % 256),
		/* H_total[15:8] */
		3, 0x92, 0x35, (u8)((T1_HBP + T1_HFP + T1_HSW + T1_HACT) / 256),
		3, 0x92, 0x36, (u8)((T1_VT_F1) % 256), /* V_total[7:0] */
		3, 0x92, 0x37, (u8)((T1_VT_F1) / 256), /* V_total[15:8] */
		3, 0x92, 0x38, (u8)(T1_VBP_F1 % 256), /* VBP[7:0] */
		3, 0x92, 0x39, (u8)(T1_VBP_F1 / 256), /* VBP[15:8] */
		3, 0x92, 0x3a, (u8)(T1_VFP_F1 % 256), /* VFP[7:0] */
		3, 0x92, 0x3b, (u8)(T1_VFP_F1 / 256), /* VFP[15:8] */
		3, 0x92, 0x3c, (u8)(T1_HBP % 256), /* T1_HBP[7:0] */
		3, 0x92, 0x3d, (u8)(T1_HBP / 256), /* T1_HBP[15:8] */
		3, 0x92, 0x3e, (u8)(T1_HFP % 256), /* T1_HFP[7:0] */
		3, 0x92, 0x3f, (u8)(T1_HFP / 256), /* T1_HFP[15:8] */
		/*
		 * reg[0x43] is checksums, it will be changed
		 * after reg[0x45] or reg[0x47] changed.
		 * regs 0x43, 0x44, 0x45, 0x47 total sum is 0x6F.
		 */
		3, 0x94, 0x43, 0x33, /* avi packet checksum ,avi_pb0 */
		3, 0x94, 0x47, T1_VIC, /* vic ,0x10: 1080P ;  0x04 : 720P */
	},
#ifdef ENABLE_720P
	{
		3, 0x92, 0x18, (u8)(T2_HSW % 256), /* hsync  */
		3, 0x92, 0x19, (u8)(T2_VSW_F1 % 256), /* vsync  */
		3, 0x92, 0x1c, (u8)(T2_HACT % 256), /* H_active[7:0]  */
		3, 0x92, 0x1d, (u8)(T2_HACT / 256), /* H_active[15:8]  */
		/* H_total[7:0] */
		3, 0x92, 0x34, (u8)((T2_HBP + T2_HFP + T2_HSW + T2_HACT) % 256),
		/* H_total[15:8] */
		3, 0x92, 0x35, (u8)((T2_HBP + T2_HFP + T2_HSW + T2_HACT) / 256),
		3, 0x92, 0x36, (u8)((T2_VT_F1) % 256), /* V_total[7:0] */
		3, 0x92, 0x37, (u8)((T2_VT_F1) / 256), /* V_total[15:8] */
		3, 0x92, 0x38, (u8)(T2_VBP_F1 % 256), /* VBP[7:0] */
		3, 0x92, 0x39, (u8)(T2_VBP_F1 / 256), /* VBP[15:8] */
		3, 0x92, 0x3a, (u8)(T2_VFP_F1 % 256), /* VFP[7:0] */
		3, 0x92, 0x3b, (u8)(T2_VFP_F1 / 256), /* VFP[15:8] */
		3, 0x92, 0x3c, (u8)(T2_HBP % 256), /* T1_HBP[7:0] */
		3, 0x92, 0x3d, (u8)(T2_HBP / 256), /* T1_HBP[15:8] */
		3, 0x92, 0x3e, (u8)(T2_HFP % 256), /* T1_HFP[7:0] */
		3, 0x92, 0x3f, (u8)(T2_HFP / 256), /* T1_HFP[15:8] */
		/*
		 * reg[0x43] is checksums, it will be changed
		 * after reg[0x45] or reg[0x47] changed.
		 * regs 0x43, 0x44, 0x45, 0x47 total sum is 0x6F.
		 */
		3, 0x94, 0x43, 0x22, /* avi packet checksum ,avi_pb0 */
		3, 0x94, 0x47, T2_VIC, /* vic ,0x10: 1080P ;  0x04 : 720P */
	},
#endif
#ifdef ENABLE_1080P
	{
		3, 0x92, 0x18, (u8)(T3_HSW % 256), /* hsync  */
		3, 0x92, 0x19, (u8)(T3_VSW_F1 % 256), /* vsync  */
		3, 0x92, 0x1c, (u8)(T3_HACT % 256), /* H_active[7:0]  */
		3, 0x92, 0x1d, (u8)(T3_HACT / 256), /* H_active[15:8]  */
		/* H_total[7:0] */
		3, 0x92, 0x34, (u8)((T3_HBP + T3_HFP + T3_HSW + T3_HACT) % 256),
		/* H_total[15:8] */
		3, 0x92, 0x35, (u8)((T3_HBP + T3_HFP + T3_HSW + T3_HACT) / 256),
		3, 0x92, 0x36, (u8)((T3_VT_F1) % 256), /* V_total[7:0] */
		3, 0x92, 0x37, (u8)((T3_VT_F1) / 256), /* V_total[15:8] */
		3, 0x92, 0x38, (u8)(T3_VBP_F1 % 256), /* VBP[7:0] */
		3, 0x92, 0x39, (u8)(T3_VBP_F1 / 256), /* VBP[15:8] */
		3, 0x92, 0x3a, (u8)(T3_VFP_F1 % 256), /* VFP[7:0] */
		3, 0x92, 0x3b, (u8)(T3_VFP_F1 / 256), /* VFP[15:8] */
		3, 0x92, 0x3c, (u8)(T3_HBP % 256), /* T1_HBP[7:0] */
		3, 0x92, 0x3d, (u8)(T3_HBP / 256), /* T1_HBP[15:8] */
		3, 0x92, 0x3e, (u8)(T3_HFP % 256), /* T1_HFP[7:0] */
		3, 0x92, 0x3f, (u8)(T3_HFP / 256), /* T1_HFP[15:8] */
		/*
		 * reg[0x43] is checksums, it will be changed
		 * after reg[0x45] or reg[0x47] changed.
		 * regs 0x43, 0x44, 0x45, 0x47 total sum is 0x6F.
		 */
		3, 0x94, 0x43, 0x25, /* avi packet checksum ,avi_pb0 */
		3, 0x94, 0x47, T3_VIC, /* vic ,0x10: 1080P ;  0x04 : 720P */
	},
#endif
};

static struct i2c_client *panel_get_i2c_client(const unsigned short i2c_addr)
{
	const __u32 num = ARRAY_SIZE(panel_i2c_client);
	__u32 i;

	for (i = 0; i < num; i++) {
		struct i2c_client *client = panel_i2c_client[i];

		if (client && (client->addr == i2c_addr))
			return client;
	}

	return NULL;
}

static s32 panel_i2c_write_reg_array(u32 tidx, u32 *reg, u32 len)
{
	u32 i = 0;

	DRM_DEBUG_KMS("%s,%d\n", __func__, __LINE__);

	while (i < len) {
		if (DCS_MDELAY_FLAG == reg[i]) {
			mdelay(reg[i + 1]);
			i += reg[i] + 2;
		} else {
			char buf[4];
			struct i2c_client *client =
				panel_get_i2c_client(reg[i + 1] >> 1);

			if (client) {
				buf[0] = reg[i + 2]; /* reg addr */
				buf[1] = reg[i + 3]; /* reg value */
				if (2 != i2c_master_send(client, buf, 2)) {
					DRM_ERROR("%d\n", __LINE__);
					return -1;
				}

				#if 0
				char read_buf[4];
				if (1 != i2c_master_send(client, &buf[0], 1)) {
					DRM_ERROR("%d\n", __LINE__);
					return -1;
				}
				read_buf[0] = 0;
				if (1 != i2c_master_recv(client, read_buf, 1)) {
					DRM_ERROR("%d\n", __LINE__);
					return -1;
				}
				pr_debug("reg[%d,%d,%d,%d], read_buf=%d\n",
					reg[i + 0],
					reg[i + 1],
					reg[i + 2],
					reg[i + 3],
					read_buf[0]);
				#endif
			} else {
				pr_err("%d: null i2c_client @%x\n",
					__LINE__, reg[i + 1] >> 1);
			}
			i += reg[i] + 1;
		}
	}
	return 0;
}

static inline struct panel_lt8912b *to_panel_lt8912b(struct drm_panel *panel)
{
	return container_of(panel, struct panel_lt8912b, base);
}

static int panel_lt8912b_disable(struct drm_panel *panel)
{
	struct panel_lt8912b *p = to_panel_lt8912b(panel);

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	if (!p->enabled)
		return 0;

	if (p->backlight) {
		p->backlight->props.power = FB_BLANK_POWERDOWN;
		backlight_update_status(p->backlight);
	}

	if (p->desc && p->desc->delay.disable)
		msleep(p->desc->delay.disable);

	p->enabled = false;

	return 0;
}

static int panel_lt8912b_unprepare(struct drm_panel *panel)
{
	struct panel_lt8912b *p = to_panel_lt8912b(panel);

	DRM_DEBUG_KMS("[%d] p->prepared:%d\n", __LINE__, p->prepared);

	if (!p->prepared)
		return 0;

	if (p->reset_gpio)
		gpio_direction_output(p->reset_gpio, 0);

	if (p->power_gpio)
		gpio_direction_output(p->power_gpio, 0);

	if (p->supply)
		regulator_disable(p->supply);

	if (p->desc && p->desc->delay.unprepare)
		msleep(p->desc->delay.unprepare);

	p->prepared = false;

	return 0;
}

static int panel_lt8912b_prepare(struct drm_panel *panel)
{
	struct panel_lt8912b *p = to_panel_lt8912b(panel);
	int err;

	DRM_DEBUG_KMS("[%d] p->prepared:%d\n", __LINE__, p->prepared);

	if (p->prepared)
		return 0;
	p->prepared = true;

	if (p->supply) {
		err = regulator_enable(p->supply);
		if (err < 0) {
			dev_err(panel->dev,
				"failed to enable supply: %d\n", err);
			return err;
		}
	}

	if (p->power_gpio) {
		gpio_direction_output(p->power_gpio, 1);
		msleep(100);
	}

	if (p->sel_gpio) {
		gpio_direction_output(p->sel_gpio, 1);
		msleep(50);
	}

	if (p->reset_gpio) {
		gpio_direction_output(p->reset_gpio, 1);
		msleep(100);
		gpio_direction_output(p->reset_gpio, 0);
		msleep(100);
		gpio_direction_output(p->reset_gpio, 1);
		msleep(100);
	}

	if (p->desc && p->desc->delay.prepare)
		msleep(p->desc->delay.prepare);

	panel_i2c_write_reg_array(3,
				  lt8912b_init_common_code,
				  ARRAY_SIZE(lt8912b_init_common_code));

	panel_i2c_write_reg_array(3,
			  lt8912b_init_code[p->modes_idx],
			  ARRAY_SIZE(lt8912b_init_code[p->modes_idx]));

	return 0;
}

static int panel_lt8912b_enable(struct drm_panel *panel)
{
	struct panel_lt8912b *p = to_panel_lt8912b(panel);

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	if (p->enabled)
		return 0;
	p->enabled = true;

	if (p->desc && p->desc->delay.enable)
		msleep(p->desc->delay.enable);

	if (p->backlight) {
		p->backlight->props.power = FB_BLANK_UNBLANK;
		backlight_update_status(p->backlight);
	}

	return 0;
}

static int panel_lt8912b_get_fixed_modes(
	struct panel_lt8912b *panel)
{
	struct drm_connector *connector = panel->base.connector;
	struct drm_device *drm = panel->base.drm;
	struct drm_display_mode *mode;
	unsigned int i, num = 0;

	if (!panel->desc)
		return 0;

	for (i = 0; i < panel->desc->num_modes; i++) {
		const struct drm_display_mode *m = panel->desc->modes + i;

		mode = drm_mode_duplicate(drm, m);
		if (!mode) {
			DRM_ERROR("failed to add mode %ux%u@%u\n",
				m->hdisplay, m->vdisplay, m->vrefresh);
			continue;
		}

		drm_mode_set_name(mode);

		drm_mode_probed_add(connector, mode);
		num++;
	}

	connector->display_info.bpc = panel->desc->bpc;
	connector->display_info.width_mm = panel->desc->size.width;
	connector->display_info.height_mm = panel->desc->size.height;

	return num;
}

static int panel_lt8912b_get_modes(struct drm_panel *panel)
{
	struct panel_lt8912b *p = to_panel_lt8912b(panel);
	int num = 0;

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	/* add hard-coded panel modes */
	num += panel_lt8912b_get_fixed_modes(p);

	return num;
}

static int panel_lt8912b_get_timings(struct drm_panel *panel,
				    unsigned int num_timings,
				    struct display_timing *timings)
{
	DRM_DEBUG_KMS("%s,%d\n", __func__, __LINE__);
	return 0;
}

static int panel_i2c_probe(struct i2c_client *client,
	const struct i2c_device_id *id)
{
	__u32 i;

	if (!client) {
		DRM_ERROR("%d: null i2c_client\n", __LINE__);
		return -1;
	}

	panel_i2c_client[0] = client;
	for (i = 1; i < ARRAY_SIZE(panel_i2c_client); i++) {
		panel_i2c_client[i] = i2c_new_dummy(client->adapter,
					client->addr + i);
		if (!panel_i2c_client[i]) {
			dev_err(&client->adapter->dev,
				"address 0x%02x unavailable\n",
				client->addr + i);
			return -EADDRINUSE;
		} else {
			dev_info(&client->adapter->dev,
				"i2c_new_dummy 0x%02x 0x%02x ok\n",
				client->addr + i, panel_i2c_client[i]->addr);
		}
	}

	dev_info(&client->adapter->dev,
		"attached panel lt8912b into i2c adapter successfully\n");

	return 0;
}

static int panel_i2c_remove(struct i2c_client *client)
{
	dev_info(&client->adapter->dev,
		"detached panel lt8912b from i2c adapter successfully\n");

	return 0;
}

static const struct i2c_device_id panel_i2c_id[] = {
	{ "lt8912b_i2c", 0 },
	{ },
};

static struct of_device_id panel_i2c_match_types[] = {
	{
		.compatible = "lt8912b_i2c",
	}, {
		/* end node */
	}
};

struct i2c_driver panel_i2c_driver = {
	.driver = {
		.name = "lt8912b_i2c",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(panel_i2c_match_types),
	},
	.id_table = panel_i2c_id,
	.probe = panel_i2c_probe,
	.remove = panel_i2c_remove,
	.command = NULL,
};

static const struct drm_panel_funcs panel_lt8912b_funcs = {
	.disable = panel_lt8912b_disable,
	.unprepare = panel_lt8912b_unprepare,
	.prepare = panel_lt8912b_prepare,
	.enable = panel_lt8912b_enable,
	.get_modes = panel_lt8912b_get_modes,
	.get_timings = panel_lt8912b_get_timings,
};

static int panel_lt8912b_probe(struct device *dev,
			       const struct panel_lt8912b_desc *desc)
{
	struct device_node *np = dev->of_node;
	struct panel_lt8912b *panel;
	enum of_gpio_flags flags;
	int err;
	struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev);

	dev_dbg(dev, "%s,%d\n", __FILE__, __LINE__);

	panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
	if (!panel)
		return -ENOMEM;

	if (i2c_add_driver(&panel_i2c_driver)) {
		DRM_ERROR("failed to register panel i2c driver\n");
		return -ENOENT;
	}

	panel->power_gpio = of_get_named_gpio_flags(
		np, "power_gpio", 0, &flags);
	DRM_DEBUG_KMS("power_gpio %d, flags 0x%x\n", panel->power_gpio, flags);
	if (!gpio_is_valid(panel->power_gpio)) {
		DRM_ERROR("power_gpio %d invalid\n", panel->power_gpio);
		panel->power_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->power_gpio, "power_gpio");
		if (err < 0) {
			DRM_ERROR("power_gpio %d request failed\n",
				panel->power_gpio);
			panel->power_gpio = 0;
		}
	}

	panel->sel_gpio =
		of_get_named_gpio_flags(np, "sel_gpio", 0, &flags);
	DRM_DEBUG_KMS("sel_gpio %d, flags 0x%x\n", panel->sel_gpio, flags);
	if (!gpio_is_valid(panel->sel_gpio)) {
		DRM_DEBUG_KMS("sel_gpio %d invalid\n", panel->sel_gpio);
		panel->sel_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->sel_gpio, "sel_gpio");
		if (err < 0) {
			DRM_DEBUG_KMS("sel_gpio %d request failed\n",
				panel->sel_gpio);
			panel->sel_gpio = 0;
		}
	}

	panel->reset_gpio = of_get_named_gpio_flags(
		np, "reset_gpio", 0, &flags);
	DRM_DEBUG_KMS("reset_gpio %d, flags 0x%x\n", panel->reset_gpio, flags);
	if (!gpio_is_valid(panel->reset_gpio)) {
		DRM_ERROR("reset_gpio %d invalid\n", panel->reset_gpio);
		panel->reset_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->reset_gpio, "reset_gpio");
		if (err < 0) {
			DRM_ERROR("power_gpio %d request failed\n",
				panel->reset_gpio);
			panel->reset_gpio = 0;
		}
	}

	dsi->lanes = desc->lanes;
	dsi->format = desc->format;
	dsi->mode_flags = desc->flag;

	panel->enabled = false;
	panel->prepared = false;
	panel->desc = desc;
	panel->dev = dev;

	drm_panel_init(&panel->base);
	panel->base.dev = dev;
	panel->base.funcs = &panel_lt8912b_funcs;

	err = drm_panel_add(&panel->base);
	if (err < 0)
		goto err_out;

	mipi_dsi_attach(dsi);
	if (err < 0)
		goto err_out;

	dev_set_drvdata(dev, panel);

	DRM_DEBUG_KMS("[%d] over.\n", __LINE__);

	return 0;

err_out:
	return err;
}

static int panel_lt8912b_remove(struct mipi_dsi_device *dsi)
{
	struct panel_lt8912b *panel = dev_get_drvdata(&dsi->dev);

	mipi_dsi_detach(dsi);
	drm_panel_detach(&panel->base);
	drm_panel_remove(&panel->base);

	panel_lt8912b_disable(&panel->base);

	if (panel->backlight)
		put_device(&panel->backlight->dev);

	return 0;
}

static void panel_lt8912b_shutdown(struct mipi_dsi_device *dsi)
{
	struct panel_lt8912b *panel = dev_get_drvdata(&dsi->dev);

	panel_lt8912b_disable(&panel->base);
}

static const struct drm_display_mode lt8912b_mode[MODES_NUM] = {
	{
		/* dclk_freq */
		.clock = T1_DCLK / 1000,
		/* width */
		.hdisplay = T1_WIDTH,
		/* hsync_start = hdisplay + hfp */
		.hsync_start = T1_WIDTH + T1_HFP,
		/* hsync_end = hdisplay + hfp + hsw */
		.hsync_end = T1_WIDTH + T1_HFP + T1_HSW,
		/* htotal = hdisplay + hfp + hsw + hbp */
		.htotal = T1_WIDTH + T1_HFP + T1_HSW + T1_HBP,
		/* height */
		.vdisplay = T1_HEIGHT,
		/* vsync_start = vdisplay + vfp */
		.vsync_start = T1_HEIGHT + T1_VFP_F1,
		/* vsync_end = vdisplay + vfp + vsw */
		.vsync_end = T1_HEIGHT + T1_VFP_F1 + T1_VSW_F1,
		/* vtotal = vdisplay + vfp + vsw + vbp */
		.vtotal = T1_HEIGHT + T1_VFP_F1 + T1_VSW_F1 + T1_VBP_F1,
		.vrefresh = 60,
	},
#ifdef ENABLE_720P
	{
		/* dclk_freq */
		.clock = T2_DCLK / 1000,
		/* width */
		.hdisplay = T2_WIDTH,
		/* hsync_start = hdisplay + hfp */
		.hsync_start = T2_WIDTH + T2_HFP,
		/* hsync_end = hdisplay + hfp + hsw */
		.hsync_end = T2_WIDTH + T2_HFP + T2_HSW,
		/* htotal = hdisplay + hfp + hsw + hbp */
		.htotal = T2_WIDTH + T2_HFP + T2_HSW + T2_HBP,
		/* height */
		.vdisplay = T2_HEIGHT,
		/* vsync_start = vdisplay + vfp */
		.vsync_start = T2_HEIGHT + T2_VFP_F1,
		/* vsync_end = vdisplay + vfp + vsw */
		.vsync_end = T2_HEIGHT + T2_VFP_F1 + T2_VSW_F1,
		/* vtotal = vdisplay + vfp + vsw + vbp */
		.vtotal = T2_HEIGHT + T2_VFP_F1 + T2_VSW_F1 + T2_VBP_F1,
		.vrefresh = 50,
	},
#endif
#ifdef ENABLE_1080P
	{
		/* dclk_freq */
		.clock = T3_DCLK / 1000,
		/* width */
		.hdisplay = T3_WIDTH,
		/* hsync_start = hdisplay + hfp */
		.hsync_start = T3_WIDTH + T3_HFP,
		/* hsync_end = hdisplay + hfp + hsw */
		.hsync_end = T3_WIDTH + T3_HFP + T3_HSW,
		/* htotal = hdisplay + hfp + hsw + hbp */
		.htotal = T3_WIDTH + T3_HFP + T3_HSW + T3_HBP,
		/* height */
		.vdisplay = T3_HEIGHT,
		/* vsync_start = vdisplay + vfp */
		.vsync_start = T3_HEIGHT + T3_VFP_F1,
		/* vsync_end = vdisplay + vfp + vsw */
		.vsync_end = T3_HEIGHT + T3_VFP_F1 + T3_VSW_F1,
		/* vtotal = vdisplay + vfp + vsw + vbp */
		.vtotal = T3_HEIGHT + T3_VFP_F1 + T3_VSW_F1 + T3_VBP_F1,
		.vrefresh = 60,
	},
#endif
};

static const struct panel_lt8912b_desc lt8912b_desc = {
	.modes = &lt8912b_mode[0],
	.num_modes = ARRAY_SIZE(lt8912b_mode),
	.bpc = 6,
	.size = {
		.width = 69,
		.height = 123,
	},
	.flag = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE,
	.lanes = 4,
	.format = MIPI_DSI_FMT_RGB888,
};

static const struct of_device_id panel_lt8912b_of_match[] = {
	{
		.compatible = "lt8912b",
		.data = &lt8912b_desc,
	}, {
		/* sentinel */
	}
};
MODULE_DEVICE_TABLE(of, panel_lt8912b_of_match);

static int panel_lt8912b_pre_probe(struct mipi_dsi_device *dsi)
{
	const struct of_device_id *id;

	if (!of_device_is_available(dsi->dev.of_node))
		return -ENODEV;

	DRM_DEBUG_KMS("%s,%d\n", __FILE__, __LINE__);
	id = of_match_node(panel_lt8912b_of_match, dsi->dev.of_node);
	if (!id)
		return -ENODEV;

	return panel_lt8912b_probe(&dsi->dev, id->data);
}

static struct mipi_dsi_driver panel_lt8912b_dsi_driver = {
	.driver = {
		.name = "panel-lt8912b",
		.of_match_table = panel_lt8912b_of_match,
	},
	.probe = panel_lt8912b_pre_probe,
	.remove = panel_lt8912b_remove,
	.shutdown = panel_lt8912b_shutdown,
};

static int __init panel_lt8912b_init(void)
{
	int err;

	DRM_DEBUG_KMS("%s,%d\n", __FILE__, __LINE__);
	err = mipi_dsi_driver_register(&panel_lt8912b_dsi_driver);
	if (err < 0)
		return err;

	return 0;
}
module_init(panel_lt8912b_init);

static void __exit panel_lt8912b_exit(void)
{
	mipi_dsi_driver_unregister(&panel_lt8912b_dsi_driver);
}
module_exit(panel_lt8912b_exit);

MODULE_AUTHOR("lomboswer <lomboswer@lombotech.com>");
MODULE_DESCRIPTION("DRM Driver for Panels lt8912b");
MODULE_LICENSE("GPL and additional rights");
